package com.jh.fcsm.common.aspect;

import com.jh.fcsm.beans.sys.SysLogOper;
import com.jh.fcsm.beans.sys.vo.LoginUser;
import com.jh.fcsm.common.annotation.OperLogAnnotation;
import com.jh.fcsm.service.sys.SysLogOperService;
import com.jh.fcsm.util.AppUserUtil;
import com.jh.fcsm.util.http.IpUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * OperLogAop 添加日志 切面
 *
 * @author szx
 * @version 1.0
 * @date 2021/12/30 15:56
 */
@Aspect
@Component
public class OperLogAop {

    private static final Logger logger = LoggerFactory.getLogger(OperLogAop.class);
    private static final String METHOD_RETURN_VALUE_VARIABLE = "ret";

    @Autowired
    SysLogOperService sysLogOperService;

    /**
     * 将 {@link OperLogAnnotation} 注解作为切点
     */
    @Pointcut("@annotation(com.jh.fcsm.common.annotation.OperLogAnnotation)")
    public void point() {
    }

    /**
     * 环绕操作
     *
     * @return 方法返回值
     */
    @Around("point()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取 HttpServletRequest 对象
        HttpServletRequest request = obtainHttpServletRequest();
        // 获取 原方法
        Method method = getMethod(joinPoint);
        // 获取 原方法结果
        Object result = joinPoint.proceed();
        // 获取 OperLog 注解信息
        OperLogAnnotation operLogAnnotation = AnnotationUtils.findAnnotation(method, OperLogAnnotation.class);
        if (operLogAnnotation != null) {
            // 对注解 OperLog 中的 notes 参数按SpEL表达式进行处理
            String notes = parseBySpEL(operLogAnnotation.value(), method, joinPoint.getArgs(), result);
            // 获取 ip
            String ip = IpUtils.getIpAddr(request);
            // 获取当前操作用户
            LoginUser loginUser = getCurrentUser();

            // 构造 SysLogOper 对象
            SysLogOper log = getSysLogOperInfo(operLogAnnotation, loginUser, notes, ip);
            logger.info("【操作日志】: {}", log);

            // 异步保存日志
            CompletableFuture.runAsync(() -> {
                sysLogOperService.saveOrUpdateSysLogOper(log);
            });
        }
        return result;
    }

    /**
     * 获取登录用户信息
     *
     * @return 当前登录用户
     */
    private LoginUser getCurrentUser() {
        Object user = AppUserUtil.getLoginAppUser();
        if (user instanceof LoginUser) {
            return (LoginUser) user;
        }
        return null;
    }

    /**
     * 构造 SysLogOper 对象
     *
     * @param operLogAnnotation 注解
     * @param loginUser         登录用户
     * @param ip                请求客户端IP
     * @return 日志类
     */
    private SysLogOper getSysLogOperInfo(OperLogAnnotation operLogAnnotation, LoginUser loginUser, String notes, String ip) {
        SysLogOper log = new SysLogOper();
        log.setOperOneModule(operLogAnnotation.module());
        log.setOperTwoModule(operLogAnnotation.submodule());
        log.setOperDetails(notes);
        log.setIp(ip);
        log.setCreateTime(new Date());
        log.setUpdateTime(new Date());

        if (loginUser != null) {
            log.setCreateUser(loginUser.getUsername());
            log.setCreateUserNickname(loginUser.getNickname());
        }
        return log;
    }

    /**
     * 获取 SPEL 表达式转化后的结果
     *
     * @param value  日志描述 SpEl表达式
     * @param method 方法对象
     * @param args   参数对象
     * @return 日志描述
     */
    private String parseBySpEL(String value, Method method, Object[] args, Object ret) {
        if (StringUtils.isEmpty(value)) {
            return "";
        }

        // 获取被拦截方法的参数列表
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] params = discoverer.getParameterNames(method);
        // 使用SpEL进行解析
        ExpressionParser parser = new SpelExpressionParser();
        // SpEL 上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        // 将方法参数放入 SpEL 上下文
        for (int i = 0; i < Objects.requireNonNull(params).length; i++) {
            context.setVariable(params[i], args[i]);
        }
        // 将方法返回值加入 SpEL 上下文
        context.setVariable(METHOD_RETURN_VALUE_VARIABLE, ret);

        // 对 notes 中的 SpEL 表达式进行解析
        return parser.parseExpression(value).getValue(context, String.class);
    }

    /**
     * 获取原方法
     *
     * @param joinPoint 切点对象
     * @return 方法对象
     */
    private Method getMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        return methodSignature.getMethod();
    }

    /**
     * 获取请求对象
     *
     * @return 返回请求对象
     */
    private HttpServletRequest obtainHttpServletRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return Objects.requireNonNull(attributes).getRequest();
    }
}
